package igloo.controller;

import igloo.Application;
import igloo.aws.IglooGlacierClient;
import igloo.aws.Region;
import igloo.model.Igloo;
import igloo.model.Job;
import igloo.model.Table;
import igloo.model.Vault;
import igloo.util.DateUtil;

import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import com.amazonaws.services.dynamodb.model.AttributeValue;
import com.amazonaws.services.dynamodb.model.DeleteItemRequest;
import com.amazonaws.services.dynamodb.model.Key;
import com.amazonaws.services.dynamodb.model.PutItemRequest;
import com.amazonaws.services.dynamodb.model.QueryRequest;
import com.amazonaws.services.dynamodb.model.QueryResult;
import com.amazonaws.services.glacier.model.CreateVaultRequest;
import com.amazonaws.services.glacier.model.CreateVaultResult;
import com.amazonaws.services.glacier.model.DeleteVaultRequest;
import com.amazonaws.services.glacier.model.InitiateJobRequest;
import com.amazonaws.services.glacier.model.InitiateJobResult;
import com.amazonaws.services.glacier.model.JobParameters;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;

public class IglooController
{
    private Igloo mIgloo;

    private Map<Vault, VaultController> mVaultControllers = new HashMap<Vault, VaultController>();
    private Map<Job, JobController> mJobControllers = new HashMap<Job, JobController>();

    public IglooController(Igloo igloo)
    {
        mIgloo = igloo;
    }

    public Igloo getIgloo()
    {
        return mIgloo;
    }

    public VaultController getVaultController(Vault vault)
    {
        return mVaultControllers.get(vault);
    }

    public List<Vault> getVaults(Region region)
    {
        return mIgloo.getVaults(region);
    }

    public List<VaultController> getVaultControllers(Region region)
    {
        List<VaultController> controllers = new LinkedList<VaultController>();
        for(VaultController c : mVaultControllers.values())
        {
            if(c.getVault().getRegion().equals(region))
                controllers.add(c);
        }

        return controllers;
    }

    public ListenableFuture<VaultController> createVault(final Region region, final String name)
    {
        final SettableFuture<VaultController> future = SettableFuture.create();

        IglooGlacierClient glacier = Application.getInstance().getGlacierClient(region);

        CreateVaultRequest request = new CreateVaultRequest();
        request.setVaultName(name);

        final ListenableFuture<CreateVaultResult> vaultFuture = glacier.createVault(request);

        Application.getInstance().getExecutor().execute(new Runnable() {
            @Override
            public void run()
            {
                try
                {
                    CreateVaultResult result = vaultFuture.get();

                    Vault vault = new Vault();
                    vault.setName(name);
                    vault.setRegion(region);
                    vault.setLocation(result.getLocation());
                    vault.setCreationDate(new Date());

                    VaultController controller = new VaultController(vault);
                    mVaultControllers.put(vault, controller);

                    mIgloo.addVault(vault);

                    Map<String, AttributeValue> item = new HashMap<String, AttributeValue>();
                    item.put("region", new AttributeValue().withS(region.name()));
                    item.put("name", new AttributeValue().withS(name));
                    item.put("location", new AttributeValue().withS(vault.getLocation()));
                    item.put("created",
                             new AttributeValue().withS(DateUtil.format(vault.getCreationDate())));
                    item.put("localUpdate",
                             new AttributeValue().withS(DateUtil.format(vault.getCreationDate())));

                    PutItemRequest request = new PutItemRequest(Table.VAULT, item);

                    Application.getInstance().getDynamoClient().putItem(request);

                    future.set(controller);
                }
                catch(Exception e)
                {
                    future.setException(e);
                }
            }
        });
        return future;
    }

    public void deleteVault(Vault vault)
    {
        mIgloo.removeVault(vault);
        mVaultControllers.remove(vault);

        AttributeValue hashKey = new AttributeValue().withS(vault.getName());
        AttributeValue rangeKey = new AttributeValue().withS(vault.getRegion().name());
        Key key = new Key(hashKey, rangeKey);

        DeleteItemRequest dir = new DeleteItemRequest(Table.VAULT, key);
        Application.getInstance().getDynamoClient().deleteItem(dir);

        DeleteVaultRequest dvr = new DeleteVaultRequest(vault.getName());
        Application.getInstance().getGlacierClient(vault.getRegion()).deleteVault(dvr);
    }

    public ListenableFuture<JobController> initiateJob(final Region region, String vaultName,
            String archiveId, final String type, final String desc)
    {
        final SettableFuture<JobController> future = SettableFuture.create();

        JobParameters params = new JobParameters();
        params.setArchiveId(archiveId);
        params.setDescription(desc);

        final InitiateJobRequest request = new InitiateJobRequest();
        request.setJobParameters(params);
        request.setVaultName(vaultName);

        Application.getInstance().getExecutor().execute(new Runnable() {
            @Override
            public void run()
            {
                ListenableFuture<InitiateJobResult> jobFuture = Application.getInstance()
                                                                           .getGlacierClient(region)
                                                                           .initiateJob(request);
                try
                {
                    InitiateJobResult result = jobFuture.get();

                    Job job = new Job();
                    job.setId(result.getJobId());
                    job.setCreationDate(new Date());
                    job.setDescription(desc);
                    job.setType(type);

                    JobController controller = new JobController(IglooController.this, job);
                    mJobControllers.put(job, controller);

                    mIgloo.addJob(job);
                }
                catch(Exception e)
                {
                    future.setException(e);
                }
            }
        });

        return future;
    }

    public void deleteJob(Job job)
    {
        mJobControllers.remove(job);
        mIgloo.removeJob(job);
    }

    /**
     * Asynchronously syncs igloo with DynamoDB
     * 
     * @return igloo future
     */
    public ListenableFuture<Igloo> sync()
    {
        final SettableFuture<Igloo> future = SettableFuture.create();

        Application.getInstance().getExecutor().execute(new Runnable() {
            @Override
            public void run()
            {
                for(Region region : Region.values())
                {
                    QueryRequest request = new QueryRequest();
                    request.setTableName(Table.VAULT);
                    request.setHashKeyValue(new AttributeValue().withS(region.name()));

                    ListenableFuture<QueryResult> queryFuture = Application.getInstance()
                                                                           .getDynamoClient()
                                                                           .query(request);
                    Futures.addCallback(queryFuture, new FutureCallback<QueryResult>() {
                        @Override
                        public void onSuccess(QueryResult result)
                        {
                            List<Map<String, AttributeValue>> items = result.getItems();
                            for(Map<String, AttributeValue> item : items)
                            {
                                if(!item.containsKey("deleted"))
                                {
                                    Vault vault = Vault.fromItem(item);
                                    VaultController controller = new VaultController(vault);
                                    mVaultControllers.put(vault, controller);
                                    mIgloo.addVault(vault);

                                    controller.sync();
                                }
                            }
                        }

                        @Override
                        public void onFailure(Throwable t)
                        {
                            t.printStackTrace();
                        }
                    }, Application.getInstance().getExecutor());
                }
            }
        });

        return future;
    }
}
